Repository navigation
fix(wasm): size the query buffer from the bytes actually written; reject embedded NUL - #173
Conversation
stringToPtr sized the buffer with lengthBytesUTF8 and wrote with stringToUTF8, which disagree on unpaired surrogates (4 bytes per two units vs 3 bytes each), so the write stopped short and the tail of the query was silently dropped. An embedded NUL likewise ended the C string early. Encode once with TextEncoder (unpaired surrogates become U+FFFD, the bytes a Postgres driver sends), allocate exactly that many bytes plus the terminator, and throw on an embedded NUL. Fixes #169
|
I'll fix CI failures and address comments from users with write access. I'll skip comments containing "(aside)".
|
|
Review complete. No issues found — approved ✅. This PR rewrites the string-to-WASM-pointer marshaling in
No reviewable defects were confirmed; all candidate issues were rejected on verification against the source. Reviewed commit: 65e440f |
…3 tests cover src/
…o's wrapper On main the v13 job runs the wrapper bundled in @libpg-query/v13@13.5.7, so the recovery test exercised the old wrapper and hung. Also bound the recovery suite with a 120 s timeout so a regression fails instead of hanging.
…uncation # Conflicts: # .github/workflows/ci.yml
TextEncoder is missing under Jest's jsdom environment, so loading the wrapper threw ReferenceError there. Replace unpaired surrogates with U+FFFD and keep Emscripten's lengthBytesUTF8/stringToUTF8, which agree on well-formed strings.
Summary
Fixes #169. Before this change, the wrapper could parse less than the query it was given and report no error.
stringToPtrsized the input buffer with Emscripten'slengthBytesUTF8and then wrote it withstringToUTF8. The two count unpaired surrogates differently:lengthBytesUTF8counts 4 bytes for every two code units.stringToUTF8writes 3 bytes for each code unit.So the buffer came out too small, the write stopped early, and the end of the query was dropped. Depending on where the cut fell, the second statement disappeared or the parser threw a bogus syntax error. An embedded
U+0000ended the C string early in the same way.The change is made in
templates/index.tsandtemplates/full/index.ts, then copied intoversions/*/src/index.ts. No WASM, C, Makefile, proto or type changes.Why not
TextEncoder: an earlier revision encoded the input withTextEncoder. Jest'sjsdomenvironment has no globalTextEncoder, sorequire('libpg-query')threwReferenceErrorthere. Published 18.1.5 works in jsdom. The regex plus Emscripten's own helpers needs no runtime global. On 1M random strings it gives the same bytes asTextEncoder.Why replace unpaired surrogates instead of throwing: Node Postgres drivers encode strings with the WHATWG UTF-8 encoder, which turns an unpaired surrogate into U+FFFD. Postgres therefore receives U+FFFD, and the parse tree now reflects exactly those bytes. UTF-16 offsets don't move, because one code unit becomes one code unit.
Why throw on NUL: Postgres can't receive one in query text. Against a real PG 14 server,
pggets08P01 invalid message formatfor a NUL in query text, on both the simple and extended protocol.Evidence
test/input-encoding.test.jswas added to everyversions/*package and wired into eachtestscript.libpg-query@18.1.5and13.7.3(same WASM, old wrapper) pass 3 of 11; the fixed wrapper passes 11/11. The new no-TextEncodertest also fails on this PR's earlierTextEncoderrevision..sqlfile in pgsql-parser (557 files, about 110k inputs per version). That is 662k comparisons, about 40k of them error results, with 0 differences:parseSync,fingerprintSync,normalizeSyncandscanSync;parseSync.jest-environment-jsdom, the fixed wrapper loads and parses.Link to Devin session: https://app.devin.ai/sessions/bcd8b2f791164247acb7a659b481efc2
Open in Devin Desktop: https://app.devin.ai/desktop/session/bcd8b2f791164247acb7a659b481efc2?variant=devin
Requested by: @pyramation